package org.elucid.gadmin.ui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JToolBar;
import javax.swing.JTree;
import javax.swing.SwingConstants;
import javax.swing.SwingWorker;
import javax.swing.border.BevelBorder;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreePath;

import org.apache.log4j.Logger;
import org.elucid.gadmin.GConnection;
import org.elucid.gadmin.GTreeItem;
import org.elucid.gadmin.Main;
import org.elucid.gadmin.ServerInfo;
import org.elucid.gadmin.Settings;
import org.elucid.gadmin.Utilities;
import org.elucid.gadmin.model.ColumnItem;
import org.elucid.gadmin.model.DatabaseItem;
import org.elucid.gadmin.model.ServerItem;
import org.elucid.gadmin.model.SettingsItem;
import org.elucid.gadmin.model.TableItem;

public class GFrame extends JFrame {

	private final Logger log = Logger.getLogger(GFrame.class);

	private static final int PATH_LENGTH_SERVER = 2;
	private static final int PATH_LENGTH_DATABASE = 3;
	private static final int PATH_LENGTH_TABLE = 4;

	private static final long serialVersionUID = -4067342923875897701L;
	private static final int DEFAULT_WIDTH = 824;
	private static final int DEFAULT_HEIGHT = 540;
	private static final int DEFAULT_X = 80;
	private static final int DEFAULT_Y = 40;

	static final int STATUS_BAR_HEIGHT = 20;
	static final int PROGRESS_WIDTH = 100;

	private Settings settings;
	private JTree tree;

	private List<GDialog> gdialogs = new ArrayList<GDialog>();

	private JTabbedPane tabPanel;

	private List<DatabaseItem> dbList;

	private JToolBar toolbar;

	private JLabel statusLabel;

	private JProgressBar progress;

	/**
	 * 
	 * @param settings
	 * @throws Exception
	 */
	public GFrame(Settings settings) throws Exception {
		this();
		this.settings = settings;

		setDbList(init());

	}

	/**
	 * 
	 * @return
	 * @throws Exception
	 */
	private List<DatabaseItem> init() throws Exception {

		toolbar = new JToolBar();

		this.getContentPane().add(toolbar, BorderLayout.NORTH);

		JButton jQueryButton = new JButton("Query");
		jQueryButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				javax.swing.SwingUtilities.invokeLater(new Runnable() {
					public void run() {

						try {
							ServerInfo serverInfo = settings.serverInfos[0]; // select first Server by default
							String databaseName = null;
							constructQueryPane(serverInfo, databaseName, "", null);
						} catch (Exception e) {
							e.printStackTrace();
						}

					}

				});
			}
		});
		toolbar.add(jQueryButton);

		JPanel statusPanel = new JPanel();
		statusPanel.setBorder(new BevelBorder(BevelBorder.LOWERED));
		this.add(statusPanel, BorderLayout.SOUTH);
		statusPanel.setPreferredSize(new Dimension(this.getWidth(), STATUS_BAR_HEIGHT));
		statusPanel.setLayout(new BoxLayout(statusPanel, BoxLayout.X_AXIS));
		setStatusLabel(new JLabel());
		getStatusLabel().setHorizontalAlignment(SwingConstants.LEFT);
		statusPanel.add(getStatusLabel());

		progress = new JProgressBar();
		progress.setMaximumSize(new Dimension(PROGRESS_WIDTH, GFrame.STATUS_BAR_HEIGHT));
		statusPanel.add(progress);

		DefaultMutableTreeNode top = new DefaultMutableTreeNode(new SettingsItem());
		List<DatabaseItem> databaseList = createDbNodes(top);
		tree = new JTree(top);
		tree.setToggleClickCount(0); // disable double-click to expand
		tree.expandRow(1);

		tree.setCellRenderer(new TreeCellRenderer() {

			@Override
			public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {

				DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
				Object userObject = node.getUserObject();

				JPanel renderer = new JPanel(new BorderLayout(5, 5));
				renderer.setBackground(Color.white);
				JLabel titleLabel = new JLabel(userObject.toString());
				titleLabel.setForeground(Color.black);
				titleLabel.setBackground(Color.white);

				JLabel imageLabel = null;
				if (userObject instanceof GTreeItem)
					imageLabel = new JLabel(((GTreeItem) userObject).getTreeIcon());
				else {
					imageLabel = new JLabel();
				}

				renderer.add(imageLabel, BorderLayout.WEST);
				renderer.add(titleLabel, BorderLayout.CENTER);

				return renderer;
			}

		});

		tree.addMouseListener(new GMouseAdapter());

		JScrollPane treeView = new JScrollPane(tree);

		treeView.setPreferredSize(new Dimension(200, 200));
		tabPanel = new JTabbedPane();
		JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, treeView, tabPanel);

		getContentPane().add(split, BorderLayout.CENTER);

		// Ask for window decorations provided by the look and feel.
		// JFrame.setDefaultLookAndFeelDecorated(true);

		return databaseList;

	}

	/**
	 * 
	 * @author george
	 * 
	 */
	public class GMouseAdapter extends MouseAdapter {

		private static final int DOUBLE_CLICK = 2;

		@Override
		public void mouseClicked(MouseEvent e) {

			try {

				if (e.getClickCount() == DOUBLE_CLICK) {
					final TreePath path = tree.getSelectionPath();

					log.info(String.format("mouseClicked, clicks=%s, pathLength=%s, lastPath=%s", e.getClickCount(), path.getPath().length, path.getLastPathComponent()));

					if (path.getPath().length == PATH_LENGTH_DATABASE) {

						DefaultMutableTreeNode serverNode = (DefaultMutableTreeNode) path.getPath()[PATH_LENGTH_SERVER - 1];
						ServerItem serverItem = (ServerItem) serverNode.getUserObject();

						DefaultMutableTreeNode databaseNode = (DefaultMutableTreeNode) path.getLastPathComponent();
						String databaseName = databaseNode.toString();

						log.info(String.format("reading tables, DB=%s", databaseName));

						progress.setIndeterminate(true);
						invalidate();
						repaint();

						new ListTablenamesTask(serverItem.getServerInfo(), databaseName, path, databaseNode).execute();

					}
					if (path.getPath().length == PATH_LENGTH_TABLE) {

						// double-click on Table - create a Query pane
						javax.swing.SwingUtilities.invokeLater(new Runnable() {
							public void run() {
								DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getPath()[PATH_LENGTH_SERVER - 1];
								ServerItem serverItem = (ServerItem) node.getUserObject();
								ServerInfo serverInfo =  serverItem.getServerInfo();

								String databaseName = path.getPath()[PATH_LENGTH_DATABASE - 1].toString();

								DefaultMutableTreeNode tableNode = (DefaultMutableTreeNode) path.getLastPathComponent();
								String tableName = tableNode.toString();

								log.info(String.format("reading table, DB=%s, table=%s", databaseName, tableName));

								String defaultTableQuery = String.format("SELECT * FROM %s; ", tableName);
								constructQueryPane(serverInfo, databaseName, defaultTableQuery, null);
							}

						});
					}
				}

			} catch (Exception e1) {
				e1.printStackTrace();
			}
		}

	}

	/**
	 * 
	 * @param serverInfo
	 * @param databaseName
	 * @param initialQuery
	 * @param title
	 * @return
	 */
	private GDialog constructQueryPane(ServerInfo serverInfo, String databaseName, String initialQuery, String title) {

		if (title == null)
			title = String.format("New Query %s*", gdialogs.size());

		if (databaseName == null)
			databaseName = Main.settings.database;

		GDialog gDialog = new GDialog(serverInfo, databaseName, gdialogs.size(), initialQuery, dbList, title);

		gdialogs.add(gDialog);
		return gDialog;
	}

	/**
	 * 
	 * @param serverName
	 * @return
	 */
	protected ServerInfo lookupServerInfo(String serverName) {
		ServerInfo info = null;
		for (ServerInfo serverInfo : settings.serverInfos) {
			if (serverInfo.getName().equals(serverName)) {
				info = serverInfo;
				break;
			}
		}
		return info;
	}

	public GFrame() {
		super("GAdmin");
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
		setLocation(DEFAULT_X, DEFAULT_Y);

		// Set the frame icon to an image loaded from a file.
		// frame.setIconImage(new ImageIcon(imgURL).getImage());

		// pack();

	}

	/**
	 * 
	 * @param top
	 * @return
	 * @throws Exception
	 */
	private List<DatabaseItem> createDbNodes(DefaultMutableTreeNode top) throws Exception {

		List<DatabaseItem> databaseList = new ArrayList<DatabaseItem>();
		for (ServerInfo serverInfo : settings.serverInfos) {
			DefaultMutableTreeNode server = new DefaultMutableTreeNode(new ServerItem(serverInfo));
			top.add(server);

			databaseList = listDatabases(serverInfo);
			for (DatabaseItem database : databaseList) {
				DefaultMutableTreeNode db = new DefaultMutableTreeNode(database);
				server.add(db);
			}

		}

		return databaseList;
	}

	/**
	 * 
	 * @param server
	 * @return
	 * @throws Exception
	 */
	private List<DatabaseItem> listDatabases(ServerInfo server) throws Exception {
		List<DatabaseItem> databases = new GConnection(server).readDatabaseList();

		return databases;
	}

	private class ListTablenamesTask extends SwingWorker {

		private ServerInfo serverInfo;
		private String databaseName;
		private TreePath path;
		private DefaultMutableTreeNode databaseNode;

		public ListTablenamesTask(ServerInfo serverInfo, String databaseName, TreePath path, DefaultMutableTreeNode databaseNode) {
			this.serverInfo = serverInfo;
			this.databaseName = databaseName;
			this.path = path;
			this.databaseNode = databaseNode;
		}

		@Override
		protected Object doInBackground() throws Exception {

			try {
				Map<TableItem, List<ColumnItem>> tableMap = null;

				tableMap = new GConnection(serverInfo).readTableList(databaseName);

				List<TableItem> tables = new LinkedList<TableItem>();

				tables.addAll(tableMap.keySet());
				Collections.sort(tables);

				for (TableItem table : tables) {
					DefaultMutableTreeNode tableNode = new DefaultMutableTreeNode(table);
					List<ColumnItem> columnList = tableMap.get(table);
					for (ColumnItem columnName : columnList) {
						DefaultMutableTreeNode columnNode = new DefaultMutableTreeNode(columnName);
						tableNode.add(columnNode);
					}

					databaseNode.add(tableNode);
				}

				tree.expandPath(path);
				tree.repaint();
				tree.invalidate();
			} finally {
				progress.setIndeterminate(false);
			}

			return null;
		}

	}

	/**
	 * 
	 * @param files
	 */
	public void openFiles(List<File> files) {
		for (final File file : files) {
			log.info(String.format("openFiles, openFile: %s ", file.getAbsolutePath()));

			javax.swing.SwingUtilities.invokeLater(new Runnable() {
				public void run() {

					String query;
					try {
						query = Utilities.readTextFile(file);
						ServerInfo serverInfo = settings.serverInfos[0]; // select first Server by default
						String databaseName = null;
						GDialog gDialog = constructQueryPane(serverInfo, databaseName, query, file.getName());
						gDialog.setFile(file);
					} catch (Exception e) {
						e.printStackTrace();
					}

				}

			});
		}
	}

	/** Returns an ImageIcon, or null if the path was invalid. */
	public static ImageIcon createImageIcon(String path) {
		java.net.URL imgURL = GFrame.class.getResource(path);
		if (imgURL != null) {
			return new ImageIcon(imgURL);
		} else {
			System.err.println("Couldn't find file: " + path);
			return null;
		}
	}

	public List<DatabaseItem> getDbList() {
		return dbList;
	}

	public void setDbList(List<DatabaseItem> dbList) {
		this.dbList = dbList;
	}

	public JLabel getStatusLabel() {
		return statusLabel;
	}

	public void setStatusLabel(JLabel statusLabel) {
		this.statusLabel = statusLabel;
	}

}
